﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace SocketAsyncTest {
    public class ThreadedBufferManagerTimeoutPooledSocketAsync {

        const string PRINT_BIND9 = "00038580000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C01100020001000151800002C011C0110001000100015180000401020304";
        
        const string PRINT_DJBDNS  = "00038500000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C011000200010003F4800002C011C0110001000100015180000401020304";
        const string PRINT_DJBDNS2 = "00030100000100000000000004686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C011000200010003F4800002C011C0110001000100015180000401020304";
        const string PRINT_PDNSD = "00038580000100010001000104686F737406646F6D61696E03746C640000010001C00C0001000100015180000401020304C00C00020001000151800002C011C0110001000100015180000401020304";
        const string PRINT_POWERDNS = "00038500000100010000000004686F737406646F6D61696E03746C640000010001C00C0001000100000078000401020304";
                                       //00030100000100000000000004686F737406646F6D61696E03746C640000010001C00C0001000100000078000401020304
        const string expectedss = PRINT_BIND9;

        const string SERVER_BIND9 = "192.168.168.81";
        const string SERVER_DJBDNS = "192.168.168.82";
        const string SERVER_PDNSD = "192.168.168.83";
        const string SERVER_POWERDNS = "192.168.168.84";
        const string SERVER_PDNSD_MAC = "192.168.168.108";


        const string dnsserver = SERVER_BIND9;

        static int TotalSendTo = 0;
        static int TotalReceiveFrom = 0;
        static int TotalReceiveTimeout = 0;
        static int TotalSocketClosed = 0;
        static int ResponseSuccess = 0;
        
        static SocketAsyncEventArgsPool SaeaPool;
        static Semaphore SemaphoreConnections;
        static BufferManager BufferManager;
        static int BufferSize = 512;

        static EventHandler<SocketAsyncEventArgs> sendToCompleted = new EventHandler<SocketAsyncEventArgs>(saea_SendToCompleted);
        static EventHandler<SocketAsyncEventArgs> receiveFromCompleted = new EventHandler<SocketAsyncEventArgs>(saea_ReceiveFromCompleted);

        internal class MyUserToker {
            internal Socket Socket;
            internal bool SocketClosed;
            internal AutoResetEvent TimeoutEvent;
        }

        public static void _Main(string[] argv) {

            ThreadedBufferManagerTimeoutPooledSocketAsync bmtpsa = new ThreadedBufferManagerTimeoutPooledSocketAsync();
            bmtpsa.Test10000();

        }

        public void Test10000() {

            // BUG 30 : 
            // int maxConnection = 10000; Everything is ok.
            //
            // int maxConnection = 30000;
            // SocketException: An invalid argument was supplied
            //  saea_SendToCompleted(Object sender, SocketAsyncEventArgs e): line 196
            //
            // netsh interface ipv4>show dynamicportrange udp
            // Protocol udp Dynamic Port Range
            // ---------------------------------
            // Start Port      : 49152
            // Number of Ports : 16384
            //
            // netsh interface ipv4>set dynamicportrange udp 1025 64510
            // Ok.
            // netsh interface ipv4>show dynamicportrange udp
            // Protocol udp Dynamic Port Range
            // ---------------------------------
            // Start Port      : 1025
            // Number of Ports : 64510
            
            int maxConnection = 40000;

            ThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool = new SocketAsyncEventArgsPool(maxConnection);
            ThreadedBufferManagerTimeoutPooledSocketAsync.SemaphoreConnections = new Semaphore(maxConnection, maxConnection);
            ThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager = new BufferManager(maxConnection * BufferSize, BufferSize);
            ThreadedBufferManagerTimeoutPooledSocketAsync.InitSaeaPool(maxConnection);

            int numSentTo = 1000000;
            for (int i = 0; i < numSentTo; i++) {

                ThreadPool.QueueUserWorkItem(new WaitCallback(TestSocketClient));

            }

            Console.WriteLine("Pool Send Receive Success Timeout Closed");
            do {

                Console.WriteLine("{0} {1} {2} {3} {4} {5}",
                    ThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count,
                    ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo,
                    ThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveFrom,
                    ThreadedBufferManagerTimeoutPooledSocketAsync.ResponseSuccess,
                    ThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveTimeout,
                    ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed
                    );

                Thread.Sleep(1000);
            } while (ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo != numSentTo);


            do {
                Console.WriteLine();
                Console.WriteLine("Saea in pool: {0}", ThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count);
                Console.WriteLine("Missing saea: {0}", maxConnection - ThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count);
                Console.WriteLine("Total sent: {0}", ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo);
                Console.WriteLine("Total received: {0}", ThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveFrom);
                Console.WriteLine("Total timeout: {0}", ThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveTimeout);
                Console.WriteLine("Total closed: {0}", ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed);
                Thread.Sleep(1000);
            } while (ThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Count != maxConnection);

        }


        private static void InitSaeaPool(int maxConnection) {

            // Preallocate pool of SocketAsyncEventArgs objects.
            for (int i = 0; i < maxConnection; i++) {
                SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                ThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.SetBuffer(e); 
                SaeaPool.Push(e);
            }

        }


        static string sendss = "00030100000100000000000004686F737406646F6D61696E03746C640000010001";
        static byte[] sends = StringToByteArray(sendss);

        public void TestSocketClient(object state) {
                             

            EndPoint endpoint = new IPEndPoint(IPAddress.Parse(dnsserver), 53);
            MyUserToker userToken = new MyUserToker();
            //userToken.Debug("TestSocketClient - new");

            ThreadedBufferManagerTimeoutPooledSocketAsync.SemaphoreConnections.WaitOne();
            SocketAsyncEventArgs e = ThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Pop();
            ThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.ResetBuffer(e);
            //e.SetBuffer(buffer, 0, sends.Length);
            Array.Copy(sends, 0, e.Buffer, e.Offset, sends.Length);
            e.SetBuffer(e.Offset, sends.Length);

            e.RemoteEndPoint = endpoint;
            e.Completed += sendToCompleted;
            e.UserToken = userToken;


            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            userToken.Socket = socket;
//            userToken.Debug("TestSocketClient - socket");


            try {
  //              userToken.Debug("TestSocketClient - SendToAsync");
                bool iopending = socket.SendToAsync(e);

                if (iopending == false) {
                    saea_SendToCompleted(this, e);
                }
            } catch (ObjectDisposedException ode) {

    //            userToken.Debug("TestSocketClient - ode");

                Interlocked.Increment(ref ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed);

                ThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);
                return;
            }

        }

        static TimeSpan x3secs = new TimeSpan(0, 0, 0, 3);
        static TimeSpan x500milli = new TimeSpan(0, 0, 0, 0, 500);
        static TimeSpan x2_500sec = new TimeSpan(0, 0, 0, 2, 500);
        static TimeSpan Timeout = x500milli;
        const bool ExecuteOnlyOnce = true;
        const bool NonSignaled = false;

        static void saea_SendToCompleted(object sender, SocketAsyncEventArgs e) {

            if (e.LastOperation != SocketAsyncOperation.SendTo) {
                throw new InvalidOperationException(e.LastOperation.ToString());
            }
            if (e.SocketError != SocketError.Success) {
                throw new SocketException((int)e.SocketError);
            }
            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }
            MyUserToker userToken = e.UserToken as MyUserToker;
      //      userToken.Debug("saea_SendToCompleted");

            if (userToken.Socket == null) {

        //        userToken.PrintDebug();

                throw new ArgumentNullException("Socket");
            }

            // update counters, print stats
            Interlocked.Increment(ref ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSendTo);

            // do the job
           // ThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.FreeBuffer(e);
            //ThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.SetBuffer(e);
            //e.SetBuffer(e.Offset, BufferSize);
            ThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.ResetBuffer(e);

            e.Completed -= sendToCompleted;
            e.Completed += receiveFromCompleted;

            try {
          //      userToken.Debug("saea_SendToCompleted - ReceiveFromAsync");
                bool iopending = userToken.Socket.ReceiveFromAsync(e);
                if (iopending == false) {
                    saea_ReceiveFromCompleted(sender, e);
                }

            } catch (ObjectDisposedException ode) {

            //    userToken.Debug("saea_SendToCompleted - ode");
                
                Interlocked.Increment(ref ThreadedBufferManagerTimeoutPooledSocketAsync.TotalSocketClosed);
                ThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);
                return;

            }

            AutoResetEvent timeoutEvent = new AutoResetEvent(NonSignaled);
            userToken.TimeoutEvent = timeoutEvent;
            //userToken.Debug("saea_SendToCompleted - timeoutevent");
            //userToken.Debug("saea_SendToCompleted - RegisterWaitForSingleObject");
            RegisteredWaitHandle handle = ThreadPool.RegisterWaitForSingleObject(
                timeoutEvent, 
                new WaitOrTimerCallback(saea_ReceiveFromTimedOut), 
                userToken,
                Timeout, 
                ExecuteOnlyOnce
             );
            //userToken.Handle = handle;
            //userToken.Debug("saea_SendToCompleted - handle");

        }

        static void saea_ReceiveFromTimedOut(object state, bool timedOut) {

            // check da bitch
            if (timedOut == false) {
                return;
            }
            if (state == null) {
                throw new ArgumentNullException("state");
            }
            MyUserToker userToken = state as MyUserToker;
            //userToken.Debug("saea_ReceiveFromTimedOut");

            // synchronise
            lock (userToken) {

                if (userToken.SocketClosed) {
                    return;
                }
                if (userToken.Socket == null) {
                    return;
                    //throw new ArgumentNullException("Socket");
                }

                // update counters, print stats
                Interlocked.Increment(ref ThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveTimeout);

                // do the job
              //  userToken.Debug("saea_ReceiveFromTimedOut - socket close");
                userToken.SocketClosed = true;
                userToken.Socket.Shutdown(SocketShutdown.Both);
                userToken.Socket.Close(0);
                userToken.Socket = null;

            }


        }

        static void saea_ReceiveFromCompleted(object sender, SocketAsyncEventArgs e) {
            
            // check da bitch
            if (e.LastOperation != SocketAsyncOperation.ReceiveFrom) {
                throw new InvalidOperationException(e.LastOperation.ToString());
            }

            if (e.SocketError == SocketError.OperationAborted) {
                ThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);
                return; // OperationAborted, get out of here !
            }

            if (e.SocketError != SocketError.Success) {
                ProcessError(e);
                return;
            }

            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }

            MyUserToker userToken = e.UserToken as MyUserToker;
            //userToken.Debug("saea_ReceiveFromCompleted");

            if (userToken.Socket == null) {
                throw new ArgumentNullException("Socket");
            }

            // synchronize
            lock (userToken) {

                if (!userToken.SocketClosed) {

                    // update counters, print stats
                    Interlocked.Increment(ref ThreadedBufferManagerTimeoutPooledSocketAsync.TotalReceiveFrom);

                    // do the job
              //      userToken.Debug("saea_ReceiveFromCompleted - Unregister");
                    if (userToken.TimeoutEvent != null) { // immediate synchronous event do not need to timeout
                        //userToken.Handle.Unregister(null);
                        userToken.TimeoutEvent.Set();
                    }

                //    userToken.Debug("saea_ReceiveFromCompleted - socket close");
                    userToken.SocketClosed = true;
                    userToken.Socket.Shutdown(SocketShutdown.Both);
                    userToken.Socket.Close(0);
                    userToken.Socket = null;

                   
                    string receivedss = ByteArrayToString(e.Buffer, e.Offset, e.BytesTransferred);
                    if (receivedss != ThreadedBufferManagerTimeoutPooledSocketAsync.expectedss) {
                        Console.WriteLine();
                        Console.WriteLine(receivedss);
                    } else {
                        Interlocked.Increment(ref ThreadedBufferManagerTimeoutPooledSocketAsync.ResponseSuccess);
                    }

                }

            }


            ThreadedBufferManagerTimeoutPooledSocketAsync.ReleaseSaea(e);

        }

        private static void ProcessError(SocketAsyncEventArgs e) {

            if (e.UserToken == null) {
                throw new ArgumentNullException("UserToken");
            }

            MyUserToker userToken = e.UserToken as MyUserToker;
            //userToken.Debug("saea_ReceiveFromCompleted");

            if (userToken.TimeoutEvent != null) { // immediate synchronous event do not need to timeout
                //userToken.Handle.Unregister(null);
                userToken.TimeoutEvent.Set();
            }

            ReleaseSaea(e);

        }

        private static void ReleaseSaea(SocketAsyncEventArgs e) {

            e.Completed -= receiveFromCompleted;
            e.Completed -= sendToCompleted;

            MyUserToker userToken = e.UserToken as MyUserToker;
            userToken.Socket = null;
            userToken.TimeoutEvent = null;
            e.UserToken = null;
            
            ThreadedBufferManagerTimeoutPooledSocketAsync.BufferManager.ResetBuffer(e);
            ThreadedBufferManagerTimeoutPooledSocketAsync.SaeaPool.Push(e);
            ThreadedBufferManagerTimeoutPooledSocketAsync.SemaphoreConnections.Release();

        }


        public static byte[] StringToByteArray(string s) {

            string hex = s.Replace(" ", "");

            int NumberChars = hex.Length;
            byte[] bytes = new byte[NumberChars / 2];
            for (int i = 0; i < NumberChars; i += 2)
                bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
            return bytes;

        }

        public static string ByteArrayToString(byte[] ba, int offset, int count) {
            string hex = BitConverter.ToString(ba, offset, count);
            return hex.Replace("-", "");
        }

    }
}
